use serde::Deserialize;
use std::{
    collections::HashMap,
    env,
    ffi::OsStr,
    io::{self, Write},
    path::Path,
    process::{Command, Stdio},
};

// 如果你没在 Cargo.toml 里加 ctrlc = "3.4"，请加上
// [dependencies]
// serde = { version = "1.0", features = ["derive"] }
// toml = "0.8"
// ctrlc = "3.4"

#[derive(Debug, Deserialize)]
struct Config {
    /// 可选：工作目录
    #[serde(default)]
    working_dir: Option<String>,
    /// 启动命令：["可执行文件", "参数1", "参数2", ...]
    start: Vec<String>,
    /// 停止命令：["可执行文件", "参数1", ...]
    stop: Vec<String>,
    /// 自定义命令映射：[commands.<关键字>] = ["可执行文件", "参数1", ...]
    commands: HashMap<String, Vec<String>>,
}

fn main() {
    // 只接受一个参数：toml 路径
    let mut args = env::args().skip(1);
    let config_path = match args.next() {
        Some(p) => p,
        None => {
            eprintln!(">>> 用法: cmd-runner <config.toml> <<<");
            std::process::exit(1);
        }
    };

    banner();

    // 读取配置
    let cfg = match load_config(&config_path) {
        Ok(c) => c,
        Err(e) => {
            err(&format!("读取配置失败: {e}"));
            std::process::exit(1);
        }
    };

    // 切换工作目录（可选）
    if let Some(dir) = &cfg.working_dir {
        if let Err(e) = env::set_current_dir(dir) {
            err(&format!("切换工作目录失败: {} -> {}", dir, e));
            std::process::exit(1);
        } else {
            info(&format!("工作目录: {}", dir));
        }
    }

    // 注册 Ctrl+C：触发时执行 stop 后退出
    let stop_on_ctrlc = cfg.stop.clone();
    if let Err(e) = ctrlc::set_handler(move || {
        warn("收到中断信号，执行停止命令...");
        if let Err(err) = run_command(&stop_on_ctrlc) {
            warn(&format!("停止命令执行失败: {err}"));
        }
        println!("<<< 已退出 <<<");
        std::process::exit(0);
    }) {
        warn(&format!("无法注册 Ctrl+C 处理器: {e}"));
    }

    // 启动命令
    info(&format!("执行启动命令: {}", pretty(&cfg.start)));
    if let Err(e) = run_command(&cfg.start) {
        warn(&format!("启动命令执行失败: {e}"));
    } else {
        println!("<<< 启动完成 <<<");
    }

    // 交互循环
    println!();
    println!(">>> 已进入交互模式。输入 'help' 查看指令，'quit' 退出。<<<");
    let mut line = String::new();
    loop {
        print!(">>> ");
        let _ = io::stdout().flush();
        line.clear();
        if io::stdin().read_line(&mut line).is_err() {
            err("读取输入失败，准备退出。");
            safe_stop_and_exit(&cfg.stop);
        }
        let input = line.trim();
        if input.is_empty() {
            continue;
        }
        if input.eq_ignore_ascii_case("quit") {
            info(&format!("执行停止命令: {}", pretty(&cfg.stop)));
            safe_stop_and_exit(&cfg.stop);
        }
        if input.eq_ignore_ascii_case("help") {
            print_help(&cfg);
            continue;
        }

        // 解析：第一个 token 是关键字，其余为附加参数
        let mut parts = shell_split(input);
        if parts.is_empty() {
            continue;
        }
        let key = parts.remove(0);
        match cfg.commands.get(&key) {
            Some(base) => {
                // 拼接配置里的参数 + 用户输入的附加参数
                let mut cmd = base.clone();
                cmd.extend(parts);
                info(&format!("执行命令: {}", pretty(&cmd)));
                if let Err(e) = run_command(&cmd) {
                    err(&format!("命令执行失败: {e}"));
                } else {
                    println!("<<< 执行完成 <<<");
                }
            }
            None => {
                warn(&format!("未知指令: {}。输入 'help' 查看可用指令。", key));
            }
        }
    }
}

/// 读取并解析 TOML
fn load_config(path: &str) -> Result<Config, String> {
    let text = std::fs::read_to_string(path)
        .map_err(|e| format!("无法读取文件 {}: {}", path, e))?;
    toml::from_str::<Config>(&text).map_err(|e| format!("TOML 解析失败: {e}"))
}

/// 执行命令（自动识别 .bat/.cmd 用 cmd /C）
fn run_command(spec: &[String]) -> Result<(), String> {
    if spec.is_empty() {
        return Err("命令为空".into());
    }
    let program = &spec[0];
    let args = &spec[1..];

    let mut cmd = if is_batch_file(program) && cfg!(windows) {
        let mut c = Command::new("cmd");
        c.arg("/C").arg(program);
        for a in args {
            c.arg(a);
        }
        c
    } else {
        let mut c = Command::new(program);
        c.args(args);
        c
    };

    // 继承 I/O，能看到子进程输出
    let status = cmd
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()
        .map_err(|e| format!("启动进程失败: {e}"))?;

    if !status.success() {
        return Err(format!("进程退出状态: {}", status));
    }
    Ok(())
}

/// 优雅退出：执行 stop 然后 exit
fn safe_stop_and_exit(stop: &[String]) -> ! {
    if let Err(e) = run_command(stop) {
        warn(&format!("停止命令执行失败: {e}"));
    }
    println!("<<< 已退出 <<<");
    std::process::exit(0);
}

/// 判断是否为批处理脚本
fn is_batch_file(path: &str) -> bool {
    Path::new(path)
        .extension()
        .and_then(OsStr::to_str)
        .map(|ext| {
            let e = ext.to_ascii_lowercase();
            e == "bat" || e == "cmd"
        })
        .unwrap_or(false)
}

/// 打印帮助
fn print_help(cfg: &Config) {
    println!("====================================");
    println!(">>> 可用指令");
    println!(">>>   help                 显示本帮助");
    println!(">>>   quit                 执行 stop 并退出");
    for key in sorted_keys(&cfg.commands) {
        if let Some(cmd) = cfg.commands.get(&key) {
            println!(">>>   {:<18} {}", key, pretty(cmd));
        }
    }
    println!(">>> 示例：");
    println!(
        ">>>   aaa arg1 arg2  -> {} + 用户参数",
        pretty(cfg.commands.get("aaa").unwrap_or(&vec![]))
    );
    println!("====================================");
}

/// 让命令看起来更友好
fn pretty(cmd: &[String]) -> String {
    cmd.iter()
        .map(|s| {
            if s.contains(' ') || s.contains('\t') {
                format!("\"{}\"", s)
            } else {
                s.clone()
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

/// 简单的 shell 风格分词：支持用引号包裹含空格的参数
fn shell_split(s: &str) -> Vec<String> {
    let mut out = Vec::new();
    let mut buf = String::new();
    let mut in_quote = false;
    let mut quote_char = '\0';

    let mut chars = s.chars().peekable();
    while let Some(ch) = chars.next() {
        match ch {
            '\'' | '"' => {
                if in_quote && ch == quote_char {
                    in_quote = false;
                } else if !in_quote {
                    in_quote = true;
                    quote_char = ch;
                } else {
                    buf.push(ch);
                }
            }
            ' ' | '\t' if !in_quote => {
                if !buf.is_empty() {
                    out.push(std::mem::take(&mut buf));
                }
            }
            _ => buf.push(ch),
        }
    }
    if !buf.is_empty() {
        out.push(buf);
    }
    out
}

fn sorted_keys(map: &HashMap<String, Vec<String>>) -> Vec<String> {
    let mut v: Vec<_> = map.keys().cloned().collect();
    v.sort();
    v
}

/// 小横幅，忍不住给你点仪式感
fn banner() {
    println!("====================================");
    println!(">>> cmd-runner ready. Type help. <<<");
    println!("====================================");
}

fn info(msg: &str) {
    println!(">>> [INFO] {msg} <<<");
}
fn warn(msg: &str) {
    eprintln!(">>> [WARN] {msg} <<<");
}
fn err(msg: &str) {
    eprintln!(">>> [ERROR] {msg} <<<");
}
